﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using JetBlack.Diagnostics;
using JetBlack.SimpleTopicBus.Messages;

namespace JetBlack.SimpleTopicBus.Distributor
{
    /// <summary>
    /// The Acceptor services client connection requests.
    /// </summary>
    internal class Acceptor : IDisposable
    {
        public event Action<Interactor> OnClientOpened;
        public event Action<Interactor> OnClientClosed;
        public event Action<Interactor, Message> OnClientMessage;

        private readonly Thread thread;
        private int nextInteractorId = 0;
        private bool isListening = true;
        private readonly TcpListener listener;
        private readonly Dictionary<Socket, Interactor> clients = new Dictionary<Socket, Interactor>();

        public Acceptor()
        {
            this.thread = new Thread(Run);
            IPEndPoint endPoint = new IPEndPoint(Dns.GetHostEntry(String.Empty).AddressList[0], AppEnvironment.Configuration.Port);
            listener = new TcpListener(endPoint);
        }

        public void Start()
        {
            thread.Start();
        }

        private void Run()
        {
            AppEnvironment.TraceSource.Verbose("Accepting clients");

            try
            {
                listener.Start();

                while (IsListening)
                {
                    AppEnvironment.TraceSource.Verbose("Waiting for client to connect");

                    Interactor interactor = HandleClientOpened();

                    lock (clients)
                        clients.Add(interactor.Socket, interactor);

                    interactor.Start();
                }
            }
            catch (Exception ex)
            {
                if (IsListening)
                    AppEnvironment.TraceSource.Error(ex);
            }

            foreach (Interactor client in clients.Values)
                client.IsConnected = false;
        }

        private Interactor HandleClientOpened()
        {
            TcpClient tcpClient = listener.AcceptTcpClient();

            Interactor interactor = new Interactor(tcpClient, nextInteractorId++);
            interactor.OnClosed += HandleClientClosed;
            interactor.OnMessage += HandleClientMessage;

            if (OnClientOpened != null)
                OnClientOpened(interactor);

            AppEnvironment.TraceSource.Verbose("Client connection received for {0}", interactor);

            return interactor;
        }

        private void HandleClientMessage(Interactor client, Message message)
        {
            if (OnClientMessage != null)
                OnClientMessage(client, message);
        }

        private void HandleClientClosed(Interactor client)
        {
            AppEnvironment.TraceSource.Verbose("Client closed connection {0}", client);
            if (OnClientClosed != null)
                OnClientClosed(client);
            client.Dispose();
            clients.Remove(client.Socket);
        }

        public void Stop()
        {
            AppEnvironment.TraceSource.Verbose("Stopping interactor");

            IsListening = false;
        }

        public bool IsListening
        {
            get { lock (this) return isListening; }
            set { lock (this) isListening = value; }
        }

        public void Dispose()
        {
            if (thread.IsAlive)
            {
                IsListening = false;
                listener.Stop();
                thread.Join();
            }
        }
    }
}
